﻿namespace Sai.UI
{
    using log4net;
    using Sai.Cecil;
    using Sai.Core;
    using Sai.Core.Reporting;
    using Sai.Core.Rules;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Text;
    using System.Windows.Forms;

	public partial class MainForm : Form
	{
        private static readonly ILog logger = LogManager.GetLogger("ApplicationLog");
        private BackgroundWorker _worker;

        private ControlStateCollection _dataEntryState = new ControlStateCollection();
        private ControlStateCollection _progressState = new ControlStateCollection();

        private ProductReleaseProject _project = null;

        private bool _isDirty = false;

        private const string ProjectFilter = "Sai Project Files (*.sai)|*.sai|All Files (*.*)|*.*";

		public MainForm()
		{
			this.InitializeComponent();
		}

		private void MainForm_Load(object sender, EventArgs e)
		{
            var builder = new ProductReleaseProjectBuilder();
            this._project = builder.CreateDefault();

            this.InitialiseUIFromProject(this._project);

            this._dataEntryState.Add(this.txtProductName);
            this._dataEntryState.Add(this.txtPreviousVersion);
            this._dataEntryState.Add(this.txtNewVersion);
            this._dataEntryState.Add(this.asPreviousVersion);
            this._dataEntryState.Add(this.asNewVersion);
            this._dataEntryState.Add(this.fsOutputFolder);
            this._dataEntryState.Add(this.cmbReportFormat);
            this._dataEntryState.Add(this.cbAdditions);
            this._dataEntryState.Add(this.cbRemovals);
            this._dataEntryState.Add(this.cbChanges);
            this._dataEntryState.Add(this.cbObsolete);
            this._dataEntryState.Add(this.btnStart);

            this._progressState.Add(this.progressBar1);
            this._progressState.Add(this.btnCancel);

            this._progressState.Invisible();

            this._worker = new BackgroundWorker()
            {
                WorkerReportsProgress = true,
                WorkerSupportsCancellation = true
            };

            this._worker.DoWork +=
                (obj, args) =>
                {
                    RunReportingProcess(obj, args);
                };

            this._worker.ProgressChanged +=
                (obj, args) =>
                {
                    if (this.progressBar1.Maximum < args.ProgressPercentage)
                    {
                        this.progressBar1.Style = ProgressBarStyle.Marquee;
                    }
                    else
                    {
                        this.progressBar1.Value = args.ProgressPercentage;
                    }
                };

            this._worker.RunWorkerCompleted +=
                (obj, args) =>
                {
                    this._progressState.Invisible();

                    if (args.Error != null)
                    {
                        MessageBox.Show(
                            args.Error.Message, 
                            "Sai.Net", 
                            MessageBoxButtons.OK, 
                            MessageBoxIcon.Error);
                    }

                    this.fileToolStripMenuItem.Enabled = this.buildToolStripMenuItem.Enabled = true;
                    this._dataEntryState.Enable();

                    if (!args.Cancelled && args.Error == null)
                    {
                        if (this._project.ReportFormat == ReportFormat.Html || this._project.ReportFormat == ReportFormat.Html4)
                        {
                            string launchPath = Path.Combine(this._project.OutputFolder, this._project.Name + ".html");

                            if (System.IO.File.Exists(launchPath))
                            {
                                Process.Start(launchPath);
                            }
                        }
                    }
                };


            this._isDirty = false;
		}

        private void btnStart_Click(object sender, EventArgs e)
        {
            this.Build();
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            if (this._worker.IsBusy)
            {
                this._worker.CancelAsync();
            }
            else
            {
                // check that project should be saved...

                this.Close();
            }
        }

        private void RunReportingProcess(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
            ProductReleaseProject project = e.Argument as ProductReleaseProject;

            int pcDone = 0;

            worker.ReportProgress(pcDone);

            try
            {
                // look for rules...

                IRulePluginFinder ruleFinder = new RulePluginReflectionFinder(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
                IRulePluginRepository repository = new RulePluginRepository();

                ruleFinder.Find(repository);
                worker.ReportProgress(++pcDone);

                var folderComparisonBuilder = new AssemblyComparisonBuilder(project);

                folderComparisonBuilder.BuildStarting += (obj, args) =>
                {
                    worker.ReportProgress(++pcDone, "Started at " + DateTime.Now);

                    args.CancelAction = worker.CancellationPending;
                };

                folderComparisonBuilder.LoadingFolder += (obj, args) =>
                {
                    worker.ReportProgress(++pcDone, "Loading folder");
                    logger.InfoFormat("Loading assemblies from {0}", args.FolderName);

                    args.CancelAction = worker.CancellationPending;
                };

                folderComparisonBuilder.BuildError += (obj, args) =>
                {
                    throw new Exception(args.Message);
                };

                folderComparisonBuilder.BuildComplete += (obj, args) =>
                {
                    worker.ReportProgress(++pcDone);
                };

                // comparison results needs packaging with summary info and changed assemblies to report on it.
                var comparisonResults = folderComparisonBuilder.Build(new NullProgressReporter());

                IWalkTypeDifferencesFactory typeDifferenceWalkerFactory = new TypeDifferenceWalkerFactory(repository);
                IWalkIntrospectedAssembliesFactory assemblyDifferenceWalkerFactory = new AssemblyIntrospectionWalkerFactory(repository);
                IProductReleaseDifferenceFactory releaseDifferenceFactory = new ProductReleaseDifferenceFactory(project);

                IAssemblyDifferenceFactory assemblyDifferenceFactory = new AssemblyDifferenceFactory();

                var introspectionBuilder = new TypeIntrospectionBuilder(
                                            new MonoCecilAssemblyIntrospector(),
                                            assemblyDifferenceWalkerFactory,
                                            typeDifferenceWalkerFactory,
                                            releaseDifferenceFactory,
                                            assemblyDifferenceFactory,
                                            comparisonResults,
                                            project);

                introspectionBuilder.AnalysingAssembly += (obj, args) =>
                {
                    worker.ReportProgress(++pcDone);
                    args.CancelAction = worker.CancellationPending;
                };

                introspectionBuilder.BuildError += (obj, args) =>
                {
                    throw new Exception(args.Message);
                };

                introspectionBuilder.Build(new NullProgressReporter());

                IReportFormatterFactory reportFactory = new ReportFormatterFactory(project);

                if (introspectionBuilder.ProductReleaseDifference != null)
                {
                    var reporter = new ReportBuilder(
                        introspectionBuilder.ProductReleaseDifference,
                        introspectionBuilder.AssemblyDifferences,
                        project,
                        reportFactory);

                    reporter.CreatingReport += (obj, args) =>
                    {
                        worker.ReportProgress(++pcDone);
                        args.CancelAction = worker.CancellationPending;
                    };

                    reporter.CreatingReport += (obj, args) =>
                    {
                        args.CancelAction = worker.CancellationPending;
                    };

                    reporter.ReportComplete += (obj, args) =>
                    {
                        args.CancelAction = worker.CancellationPending;
                    };

                    reporter.BuildError += (obj, args) =>
                    {
                        throw new Exception(args.Message);
                    };

                    reporter.BuildEvent += (obj, args) =>
                    {
                        args.CancelAction = worker.CancellationPending;
                    };

                    reporter.BuildComplete += (obj, args) =>
                    {
                        e.Result = "Sai Done.";
                    };

                    reporter.Build(new NullProgressReporter());
                }
            }
            catch (BuildCancelledException)
            {
                // don't need to report this.
                e.Cancel = true;
            }
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // check if dirty ???
            // before closing...
            if (this._worker.IsBusy)
            {
                // set pending flag...
                this._worker.CancelAsync();
            }
            else
            {
                // check with 
                this.Close();
            }
        }

        private void openProjectToolStripMenuItem_Click(object sender, EventArgs e)
        {
            bool okToContinue = SaveCurrentProjectIfRequired();

            if (okToContinue)
            {
                string loadProject = PromptForFileNameToLoadProject();

                if (!string.IsNullOrEmpty(loadProject))
                {
                    try
                    {
                        ProductReleaseProjectBuilder builder = new ProductReleaseProjectBuilder();
                        this._project = builder.CreateFromFile(loadProject);
                        InitialiseUIFromProject(this._project);

                        this._isDirty = false;
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show("Unable to open project.\r\n" + ex.GetBaseException().Message);
                    }
                }
            }
        }

        private void saveProjectToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this._project = UpdateProjectFromUI();

            if (string.IsNullOrEmpty(this._project.FileName))
            {
                string saveProject = PromptForFileNameToSaveProject(this._project.Name);

                if (!string.IsNullOrEmpty(saveProject))
                {
                    this._project.FileName = saveProject;
                }
            }

            if (!string.IsNullOrEmpty(this._project.FileName))
            {
                this._project.Save();
                this._isDirty = false;
            }
        }

        private void saveProjectAsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this._project = UpdateProjectFromUI();

            string saveProject = PromptForFileNameToSaveProject(this._project.FileName);

            if (!string.IsNullOrEmpty(saveProject))
            {
                this._project.FileName = saveProject;
                this._project.Save();
                this._isDirty = false;
            }
        }

        private void InitialiseUIFromProject(ProductReleaseProject project)
        {
            this.txtProductName.Text = project.Name;

            this.txtPreviousVersion.Text = project.FromVersion;
            this.txtNewVersion.Text = project.ToVersion;

            this.asPreviousVersion.Initialise(project.FromAssemblies);
            this.asNewVersion.Initialise(project.ToAssemblies);

            this.fsOutputFolder.FolderPath = project.OutputFolder;

            this.cmbReportFormat.ReportFormat = project.ReportFormat;
            
            this.cbAdditions.Checked = project.ReportOnAdditions;
            this.cbRemovals.Checked = project.ReportOnRemovals;
            this.cbChanges.Checked = project.ReportOnChanges;
            this.cbObsolete.Checked = project.ReportOnObsolete;
        }

        private ProductReleaseProject UpdateProjectFromUI()
        {
            ProductReleaseProjectBuilder builder = new ProductReleaseProjectBuilder();

            ProductReleaseProject refreshedProject = builder.Create(
                this.txtProductName.Text,
                new Version ("1.0.0.0"),
                this.txtPreviousVersion.Text,
                string.Empty,
                this.txtNewVersion.Text,
                string.Empty,
                this.fsOutputFolder.FolderPath,
                cmbReportFormat.ReportFormat,
                false,
                string.Empty,
                string.Empty,
                cbRemovals.Checked,
                cbAdditions.Checked,
                cbChanges.Checked,
                cbObsolete.Checked);

            refreshedProject.AddFromFiles(this.asPreviousVersion.SelectedAssemblies);
            refreshedProject.AddToFiles(this.asNewVersion.SelectedAssemblies);

            return refreshedProject;
        }

        private List<string> ValidateProject(ProductReleaseProject project)
        {
            List<string> validationErrors = new List<string>();

            if (string.IsNullOrEmpty(project.Name))
            {
                validationErrors.Add("Please give the project a name.");
            }

            if (string.IsNullOrEmpty(project.FromVersion))
            {
                validationErrors.Add("Please name the \"old\" version of the product.");
            }

            if (string.IsNullOrEmpty(project.FromFolder) && !project.FromAssemblies.Any())
            {
                validationErrors.Add("You have not selected an old version of your assemblies to analyse.");
            }

            if (string.IsNullOrEmpty(project.ToVersion))
            {
                validationErrors.Add("Please name the \"new\" version of the product.");
            }

            if (string.IsNullOrEmpty(project.ToFolder) && !project.ToAssemblies.Any())
            {
                validationErrors.Add("You have not selected a new version of your assemblies to analyse.");
            }

            if (string.IsNullOrEmpty(project.OutputFolder))
            {
                validationErrors.Add("Output folder is not set.");
            }
            
            if (!project.ReportOnAdditions 
                && !project.ReportOnChanges
                && !project.ReportOnObsolete
                && !project.ReportOnRemovals)
            {
                validationErrors.Add("You have not selected any reporting options.");
            }

            return validationErrors;
        }

        private void cbAdditions_CheckedChanged(object sender, EventArgs e)
        {
            //this._project.ReportOnAdditions = cbAdditions.Checked;
            this._isDirty = true;
        }

        private void cbRemovals_CheckedChanged(object sender, EventArgs e)
        {
            //this._project.ReportOnRemovals = cbRemovals.Checked;
            this._isDirty = true;
        }

        private void cbChanges_CheckedChanged(object sender, EventArgs e)
        {
            //this._project.ReportOnChanges = cbChanges.Checked;
            this._isDirty = true;
        }

        private void cbObsolete_CheckedChanged(object sender, EventArgs e)
        {
            //this._project.ReportOnObsolete = cbObsolete.Checked;
            this._isDirty = true;
        }

        private void txtProductName_TextChanged(object sender, EventArgs e)
        {
            this._isDirty = true;
        }

        private void txtPreviousVersion_TextChanged(object sender, EventArgs e)
        {
            this._isDirty = true;
        }

        private void txtNewVersion_TextChanged(object sender, EventArgs e)
        {
            this._isDirty = true;
        }

        private void cmbReportFormat_SelectedIndexChanged(object sender, EventArgs e)
        {
            this._isDirty = true;
        }

        private void asPreviousVersion_ListChanged(object sender, EventArgs e)
        {
            this._isDirty = true;
        }

        private void asNewVersion_ListChanged(object sender, EventArgs e)
        {
            this._isDirty = true;
        }

        private string PromptForFileNameToLoadProject()
        {
            using (OpenFileDialog dialog = new OpenFileDialog())
            {
                dialog.Title = "Open Project File";
                dialog.Filter = ProjectFilter;

                if (DialogResult.OK == dialog.ShowDialog())
                {
                    return dialog.FileName;
                }

                return null;
            }
        }

        private string PromptForFileNameToSaveProject(string suggestedName = null)
        {
            using (SaveFileDialog dialog = new SaveFileDialog())
            {
                dialog.Title = "Save Project File";
                dialog.Filter = ProjectFilter;

                if (!string.IsNullOrEmpty(suggestedName))
                {
                    dialog.FileName = suggestedName;
                }

                if (DialogResult.OK == dialog.ShowDialog())
                {
                    return dialog.FileName;
                }

                return null;
            }
        }

        private DialogResult AskToSaveProject()
        {
            return MessageBox.Show("Save changes to this project?", 
                "Sai", 
                MessageBoxButtons.YesNoCancel, 
                MessageBoxIcon.Question, 
                MessageBoxDefaultButton.Button1);
        }

        private void newProjectToolStripMenuItem_Click(object sender, EventArgs e)
        {
            bool okToContinue = SaveCurrentProjectIfRequired();

            if (okToContinue)
            {
                ProductReleaseProjectBuilder builder = new ProductReleaseProjectBuilder();
                this._project = builder.CreateDefault();
                InitialiseUIFromProject(this._project);

                this._isDirty = false;
            }
        }

        private bool SaveCurrentProjectIfRequired()
        {
            if (!this._isDirty)
                return true;

            DialogResult result = AskToSaveProject();

            if (result == DialogResult.Yes)
            {
                this._project = UpdateProjectFromUI();

                if (string.IsNullOrEmpty(this._project.FileName))
                {
                    string saveProject = PromptForFileNameToSaveProject(this._project.Name);

                    if (!string.IsNullOrEmpty(saveProject))
                    {
                        this._project.FileName = saveProject;
                    }
                    else
                    {
                        // not saving...
                        return false;
                    }
                }

                if (!string.IsNullOrEmpty(this._project.FileName))
                {
                    this._project.Save();
                    this._isDirty = false;
                }
            }
            else if (result == DialogResult.Cancel)
            {
                // quit.
                return false;
            }

            return true;
        }

        private void buildToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            this.Build();
        }

        private void Build()
        {
            this._project = UpdateProjectFromUI();

            var errors = this.ValidateProject(this._project);

            if (errors.Any())
            {
                StringBuilder messageBuilder = new StringBuilder();

                foreach (var error in errors)
                {
                    messageBuilder.AppendLine(error);
                }

                MessageBox.Show(messageBuilder.ToString());
                return;
            }

            // auto save...
            if (this._isDirty && !string.IsNullOrEmpty(this._project.FileName) && File.Exists(this._project.FileName))
            {
                this._project.Save();
            }

            this.fileToolStripMenuItem.Enabled = this.buildToolStripMenuItem.Enabled = false;
            this._dataEntryState.Disable();

            this._progressState.Visible();

            int steps = 5; // find rules, analyse folders...

            // analysis & reporting
            steps += this.asPreviousVersion.SelectedAssemblies.Count * 2;
            steps += this.asNewVersion.SelectedAssemblies.Count * 2;

            // need to know how many classes have changed !

            ++steps; // final report summary

            this.progressBar1.Minimum = 0;
            this.progressBar1.Value = 0;
            this.progressBar1.Maximum = steps;

            this._worker.RunWorkerAsync(this._project); 
        }

        private void txtProductName_Validated(object sender, EventArgs e)
        {
            string validationMessage = string.IsNullOrEmpty(this.txtProductName.Text) ? "Please provide a name for the product" : string.Empty;
            
            this.productNameErrorProvider.SetError(this.txtProductName, validationMessage);
        }

        private void txtPreviousVersion_Validated(object sender, EventArgs e)
        {
            string validationMessage = string.IsNullOrEmpty(this.txtPreviousVersion.Text) ? "Please provide a name for the previous version" : string.Empty;

            this.previousVersionErrorProvider.SetError(this.txtPreviousVersion, validationMessage);
        }

        private void txtNewVersion_Validated(object sender, EventArgs e)
        {
            string validationMessage = string.IsNullOrEmpty(this.txtNewVersion.Text) ? "Please provide a name for the new version" : string.Empty;

            this.newVersionErrorProvider.SetError(this.txtNewVersion, validationMessage);
        }

	}
}
